const setCookieParser = require("set-cookie-parser");
const FormData = require("form-data");
const XLSX = require("xlsx");
const path = require("path");
const fs = require("fs");
const buffer = require("buffer");
const rn = require("random-number");
const QueuePromise = require("promise-queue");
const Pinyin = require("pinyin");
const Kuroshiro = require("kuroshiro/src").default;
const CombinedStream = require('combined-stream');
const Toolbox = require("../../tool/toolbox");
const CommonAHG = require("../../tool/CommonAxerrHandlerGenerator").CommonAxerrHandlerGen;
// const setCookieParser = require("set-cookie-parser")

class KDV2 {
  constructor() {
    this.app = new Kdv2App(this);
    this.random_xlsx_queue = new QueuePromise(1, Infinity);
    this.wps_sid = "";
    this.csrf_token = CreateCsrfToken();
    this.mycloud_groupid = 0;
    this.axios = require("axios").default.create({
      headers: {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36 OPR/68.0.3618.173",
        "accept": "*/*",
        "accept-encoding": "gzip, deflate, br",
        "accept-language": "zh-CN,zh;q=0.9",
        // "connection": "upgrade",
        // "cache-control": "max-age=0",
        "upgrade-insecure-requests": "1",
        "content-type": "application/json;charset=utf-8",
        'sec-fetch-dest': 'iframe',
        'sec-fetch-mode': 'navigate',
        'sec-fetch-site': 'same-origin'
      },
      maxContentLength: Number.MAX_SAFE_INTEGER,
      maxRedirects: 0,
      validateStatus: s => s == 200
    });
    this.__appendCookies = [{
      name: "Default",
      value: "DESC-mtime"
    }, {
      name: "lang",
      value: "zh-CN"
    }, {
      name: "weboffice_cdn",
      value: "3"
    }];
    this.memories = {
      office_csrf_token: ""
    };

    (doitnow => {
      this.getOfficeCsrfToken();
      setInterval(() => this.getOfficeCsrfToken(), 6e5);
    })();
  }

  get cookie_as_header() {
    return `wps_sid=${
      this.wps_sid
      }; csrf=${
      this.csrf_token
      }; ${this.__appendCookies.map(e => `${e.name}=${e.value}`).join('; ')}`
  }

  /**
   * @returns {Promise<{ok:Boolean,msg:String,
   * data:{
   * url:String,
   * fileinfo:{
   * fileid:Number,
   * groupid:Number,
   * parentid:Number,
   * fname:String,
   * ctime:Date,
   * mtime:Date,
   * fver:Number,
   * fsha:String
   * }
   * }}>}
   * @param {Number} fileid 
   */
  getDownloadInfo(fileid) {
    this.USELESS_user_config(`https://www.kdocs.cn/view/p/${fileid}?from=docs&source=docsWeb&order=DESC`)
    this.USELESS_tags_of_files([fileid], `https://www.kdocs.cn/view/p/${fileid}?from=docs&source=docsWeb&order=DESC`)
    return new Promise(resolve => {
      this.axios.get(`https://www.kdocs.cn/kdocs/api/preview/v1/get_down_file_info?fid=${fileid}`, {
        headers: {
          cookie: this.cookie_as_header,
          referer: `https://www.kdocs.cn/view/p/${fileid}?from=docs&source=docsWeb&order=DESC`
        }
      }).then(axresp => {
        if (axresp.data.result == "ok") {
          return resolve({
            ok: true,
            msg: "ok",
            data: {
              url: axresp.data.downloadinfo.url,
              fileinfo: {
                fileid: Number(axresp.data.fileinfo.fileid),
                groupid: Number(axresp.data.fileinfo.groupid),
                parentid: Number(axresp.data.fileinfo.parent),
                fname: axresp.data.fileinfo.fname,
                ctime: new Date(axresp.data.fileinfo.ctime * 1000),
                mtime: new Date(axresp.data.fileinfo.mtime * 1000),
                fver: axresp.data.fileinfo.fver,
                fsha: axresp.data.fileinfo.fsha
              }
            }
          })
        }
        throw axresp.data;
        debugger
      }).catch(axerr => {
        CommonAHG(resolve)(axerr);
      })
    })
  }

  /**
   * @returns {Promise<{
   * ok:Boolean,msg:String,
   * data:{
   * used:Number,
   * total:Number
   * }
   * }>}
   * @param {String} referer_str
   */
  getSpaces(referer_str) {
    return new Promise(resolve => {
      this.axios.get(`https://www.kdocs.cn/3rd/drive/api/v3/spaces`, {
        headers: {
          cookie: this.cookie_as_header,
          referer: referer_str || "https://www.kdocs.cn/?show=all"
        }
      }).then(axresp => {
        if (axresp.data && axresp.data.total) {
          return resolve({
            ok: true,
            msg: "ok",
            data: {
              total: axresp.data.total,
              used: axresp.data.used
            }
          })
        }
        throw axresp.data;
        debugger
      }).catch(axerr => {
        CommonAHG(resolve)(axerr);
      })
    })
  }

  /**
   * @returns {Promise<{ok:Boolean,msg:String,data:{
   * path:Array<{
   * fileid:Number,
   * fname:String,
   * type:"folder"|"file"
   * }>
   * }}>}
   * @param {Number} fileid 
   */
  getPath(fileid) {
    return new Promise(resolve => {
      this.axios.get(`https://www.kdocs.cn/3rd/drive/api/v5/groups/${this.mycloud_groupid}/files/${
        fileid
        }/path`, {
        headers: {
          cookie: this.cookie_as_header,
          referer: `https://www.kdocs.cn/mine/${fileid}`
        }
      }).then(axresp => {
        if (axresp.data.result == "ok") {
          return resolve({
            ok: true,
            msg: "ok",
            data: {
              path: axresp.data.path.map(p => {
                return {
                  fileid: p.fileid,
                  fname: p.fileid,
                  type: p.type
                }
              })
            }
          })
        }
        throw axresp.data;
        // debugger
      }).catch(axerr => {
        CommonAHG(resolve)(axerr)
      })
    })
  }

  /**
   * @returns {Promise<{ok:Boolean,msg:String,
   * data:{
   * files:Array<{
   * fname:String,
   * mtime:Date,
   * fileid:Number,
   * fver:Number,
   * fsha:String,
   * ftype:"file"|"folder"
   * }>,
   * next_offset:Number
   * }}>}
   * @param {Number} folder_id 
   * @param {Number} offset 
   * @param {Number} count 
   * @param {"mtime"|"fname"|"fsize"} orderby 
   * @param {"DESC"|"ASC"} order 
   */
  getFilesListInFolder(folder_id, offset = 0, count = 20, orderby = "mtime", order = "DESC") {
    return new Promise(resolve => {
      // this.getPath()
      this.USELESS_metadata_of_folder(folder_id);
      this.USELESS_tags_of_files([folder_id], `https://www.kdocs.cn/mine/${folder_id}`)
      this.axios.get(`https://www.kdocs.cn/3rd/drive/api/v5/groups/${this.mycloud_groupid}/files`, {
        params: {
          linkgroup: true,
          include: 'acl,pic_thumbnail',
          offset: offset,
          count: count,
          orderby: orderby,
          order: order,
          append: false,
          parentid: folder_id,
          reset: true
        },
        headers: {
          cookie: this.cookie_as_header,
          referer: `https://www.kdocs.cn/mine/${folder_id}`
        }
      }).then(axresp => {
        // debugger
        if (axresp.data.result == "ok") {
          let fileids = axresp.data.files.map(f => f.id);
          // debugger
          this.USELESS_tags_of_files(fileids, `https://www.kdocs.cn/mine/${folder_id}`, true)
          return resolve({
            ok: true,
            msg: "ok",
            data: {
              next_offset: axresp.data.next_offset,
              files: axresp.data.files.map(f => {
                return {
                  fname: f.fname,
                  mtime: new Date(f.mtime * 1000),
                  fileid: f.id,
                  fver: f.fver,
                  fsha: f.fsha,
                  ftype: f.ftype
                }
              })
            }
          })
        }
        throw axresp.data;
      }).catch(axerr => {
        CommonAHG(resolve)(axerr);
      })
    })
  }

  /**
   * @returns {Promise<{ok:Boolean,msg:String,data:{
   * histories:Array<{
   * fsha:String,
   * fsize:Number,
   * fver:Number,
   * history_id:Number,
   * mtime:Date
   * }>
   * }}>}
   * @param {Number} fileid 
   * @param {Number} offset 
   * @param {Number} count 
   */
  getHistories(fileid, offset = 0, count = 20) {
    return new Promise(resolve => {
      this.axios.get(`https://www.kdocs.cn/3rd/drive/api/v3/files/${fileid}/histories`, {
        params: {
          offset: offset,
          count: count,
          groupid: this.mycloud_groupid
        },
        headers: {
          cookie: this.cookie_as_header,
          referer: `https://www.kdocs.cn/?show=all`
        }
      }).then(axresp => {
        if (axresp.data.result == "ok") {
          return resolve({
            ok: true, msg: "ok",
            data: {
              histories: axresp.data.histories.map(h => {
                return {
                  fsha: h.fsha,
                  fsize: h.fsize,
                  fver: h.fver,
                  history_id: h.id,
                  mtime: new Date(h.mtime * 1000)
                }
              })
            }
          })
        }
        debugger
        throw axresp.data;
      }).catch(axerr => {
        CommonAHG(resolve)(axerr);
      })
    })
  }

  /**
   * @returns {Promise<{
   * ok:Boolean,msg:String,
   * data:{
   * sha1:String,
   * url:String
   * }
   * }>}
   * @param {Number} fileid 
   * @param {Number} history_id  
   */
  getDownloadUrlByHistoryId(fileid, history_id) {
    if (history_id == 0) {
      return new Promise(async resolve => {
        //**取最后一个版本 */
        let oget = await this.getDownloadUrlOfFile(fileid);
        return resolve(oget)
      })
    }
    return new Promise(resolve => {
      this.axios.get(`https://www.kdocs.cn/3rd/drive/api/v3/files/${fileid}/histories/${history_id}/download`, {
        params: {
          groupid: this.mycloud_groupid
        },
        headers: {
          cookie: this.cookie_as_header,
          referer: `https://www.kdocs.cn/?show=all`
        }
      }).then(axresp => {
        // debugger
        if (axresp.data.result == "ok") {
          return resolve({
            ok: true,
            msg: "ok",
            data: {
              url: axresp.data.fileinfo.url,
              sha1: axresp.data.fileinfo.sha1
            }
          })
        }
        throw axresp.data;
      }).catch(axerr => {
        CommonAHG(resolve)(axerr);
      })
    })
  }

  /**
   * @returns {Promise<{ok:Boolean,msg:String,data:{
   * download_url:String
   * }}>}
   * @param {Number} fileid 
   * @param {Number} fver 
   */
  getDownloadByFver_OfficeApi(fileid, fver = 1) {
    return new Promise(resolve => {
      let req_headers = {
        cookie: this.cookie_as_header,
        'x-user-query': `version=${fver}`,
        referer: `https://www.kdocs.cn/office/s/${this.mycloud_groupid}-${fileid}?version=${fver}`,
      }
      if (this.memories.office_csrf_token) {
        req_headers['x-csrf-rand'] = this.memories.office_csrf_token
      }
      this.axios.get(`https://www.kdocs.cn/api/office/file/${this.mycloud_groupid}-${fileid}/version/${fver}`, {
        headers: {
          ...req_headers
        }
      }).then(axresp => {
        // debugger
        if (axresp.data.file &&
          axresp.data.file.download_url) {
          return resolve({
            ok: true, msg: "ok",
            data: {
              download_url: axresp.data.file.download_url
            }
          })
        }
        throw axresp.data;
      }).catch(axerr => {
        CommonAHG(resolve)(axerr);
      })
    })
  }

  /**
   * @returns {Promise<{ok:Boolean,msg:String,data:{
   * office_csrf_token:String
   * }}>}
   */
  getOfficeCsrfToken() {
    return new Promise(resolve => {
      this.axios.post(`https://www.kdocs.cn/api/office/csrf_token`, "", {
        headers: {
          cookie: this.cookie_as_header,
          origin: "https://www.kdocs.cn",
          'x-user-query': 'version=1'
        }
      }).then(axresp => {
        if (axresp.data && axresp.data.token) {
          this.memories.office_csrf_token = axresp.data.token;
          return resolve({
            ok: true,
            msg: "ok",
            data: {
              office_csrf_token: axresp.data.token
            }
          })
        }
        // debugger
        throw axresp.data;
      }).catch(axerr => {
        CommonAHG(resolve)(axerr);
      })
    })
  }

  /**
   * @returns {Promise<{
    * ok:Boolean,msg:String,
    * data:{
    * sha1:String,
    * url:String
    * }
    * }>}
   * @param {Number} fileid 
   */
  getDownloadUrlOfFile(fileid) {
    return new Promise(resolve => {
      this.axios.get(`https://www.kdocs.cn/3rd/drive/api/v3/groups/${this.mycloud_groupid}/files/${
        fileid
        }/download?isblocks=false`, {
        headers: {
          cookie: this.cookie_as_header,
          referer: `https://www.kdocs.cn/?show=all`
        }
      }).then(axresp => {
        // debugger
        if (axresp.data && axresp.data.fileinfo && axresp.data.fileinfo.url) {
          return resolve({
            ok: true, msg: "ok",
            data: {
              sha1: axresp.data.fileinfo.sha1,
              url: axresp.data.fileinfo.url
            }
          })
        }
        throw axresp.data;
      }).catch(axerr => {
        CommonAHG(resolve)(axerr);
      })
    })
  }

  /**
   * @returns {Promise<{ok:Boolean,msg:String,
   * data:{
   * KSSAccessKeyId:String,
   * Policy:String,
   * Signature:String,
   * key:String,
   * store:"ks3",
   * url:String,
   * ["x-kss-newfilename-in-body"]:String,
   * ["x-kss-server-side-encryption"]:String
   * }}>}
   * @param {Number} folderid 
   * @param {Number} size 
   * @param {String} fname 
   */
  requestUploadToken(folderid = 0, size = 0, fname = "N.xlsx") {
    if (size <= 1) {
      size = parseInt(Math.random() * 100) + 2
    }
    return new Promise(resolve => {
      this.axios.get(`https://www.kdocs.cn/3rd/drive/api/files/upload/request`, {
        params: {
          groupid: this.mycloud_groupid,
          parentid: folderid,
          size: size,
          name: fname,
          store: 'ks3',
          method: 'POST',
          encrypt: true,
        },
        headers: {
          cookie: this.cookie_as_header,
          referer: folderid ? `https://www.kdocs.cn/mine/${folderid}` : "https://www.kdocs.cn/?show=all"
        }
      }).then(axresp => {
        // debugger
        if (axresp.data
          && axresp.data.result == "ok"
          && axresp.data.store == "ks3") {
          return resolve({
            ok: true,
            msg: "ok",
            data: {
              key: axresp.data.key,
              KSSAccessKeyId: axresp.data.KSSAccessKeyId,
              Policy: axresp.data.Policy,
              Signature: axresp.data.Signature,
              store: axresp.data.store,
              url: axresp.data.url,
              "x-kss-newfilename-in-body": axresp.data["x-kss-newfilename-in-body"],
              "x-kss-server-side-encryption": axresp.data["x-kss-server-side-encryption"]
            }
          })
        }

        throw axresp.data;
      }).catch(axerr => {
        CommonAHG(resolve)(axerr);
      })
    })
  }

  /**
   * @returns {Promise<{ok:Boolean,
    * msg:String,
    * data:{
    * newfilename_sha1:String,
    * etag:String,
    * name:String,
    * size:Number
    * }}>}
   * @param {String} filepath 
   * @param {Number} folder_id 
   * @param {Boolean} asVer2SmallPart 用来控制上传大小,只读取前面的头部,作为新版本
   */
  formUploadNormalFile(filepath, folder_id = 0, asVer2SmallPart = false) {
    let referer_str = folder_id ?
      `https://www.kdocs.cn/mine/${folder_id}`
      : "https://www.kdocs.cn/?show=all";
    let fpParsed = path.parse(filepath);
    return new Promise(async resolve => {
      if (fpParsed.base.endsWith(".rar")) {
        let o_uprar = await this.formUploadRarFileAsXlsx(filepath, folder_id);
        if (!o_uprar.ok) {
          return resolve({
            ok: false, msg: `upload RAR fail:${o_uprar.msg}`
          })
        }
        return resolve({
          ok: true, msg: "ok",
          data: {
            newfilename_sha1: o_uprar.data.newfilename_sha1,
            etag: o_uprar.data.etag,
            name: o_uprar.data.nameAsXlsx,
            size: o_uprar.data.combineSize
          }
        })
      }
      if (fpParsed.base.endsWith(".mkv")
        || fpParsed.base.endsWith(".mp4")
        || fpParsed.base.endsWith(".ts")
        || fpParsed.base.endsWith(".wmv")
      ) {
        return resolve({
          ok: false,
          msg: `DO NOT UPLOAD video:${fpParsed.base}`
        })
      }

      let o_stats = await Toolbox.getStats(filepath);
      if (!o_stats.ok) {
        return resolve({
          ok: false, msg: `stats fail:${o_stats.msg}`
        })
      }
      if (o_stats.stats.size >= 1024 * 1024 * 1024) {
        return resolve({
          ok: false,
          msg: `文件太大 超过1GB`
        })
      }
      let bytesEnd = o_stats.stats.size - 1;
      if (asVer2SmallPart) {
        bytesEnd = Math.min(bytesEnd - 1, rn({ integer: true, min: 10, max: 3 * 1024 }));
        if (bytesEnd <= 0) {
          bytesEnd = 0;
        }
      }
      let o_token = await this.requestUploadToken(folder_id, bytesEnd + 1, fpParsed.base);
      if (!o_token.ok) {
        return resolve({
          ok: false,
          msg: `fail to get token:${o_token.msg}`
        })
      }
      let st = fs.createReadStream(filepath, { start: 0, end: bytesEnd });
      st.on("error", (err) => {
        return resolve({
          ok: false,
          msg: ` stream error:${err.message}`
        })
      });
      let form = new FormData;
      form.append("key", o_token.data.key);
      form.append("Policy", o_token.data.Policy);
      form.append("submit", "Upload to KS3");
      form.append("Signature", o_token.data.Signature);
      form.append("KSSAccessKeyId", o_token.data.KSSAccessKeyId);
      form.append("x-kss-newfilename-in-body", o_token.data["x-kss-newfilename-in-body"]);
      form.append("x-kss-server-side-encryption", o_token.data["x-kss-server-side-encryption"]);
      form.append("file", st, {
        filename: fpParsed.base,
        contentType: "application/octet-stream"
      })
      let url = require("url");
      let parsed = url.parse(o_token.data.url);
      form.submit({
        host: parsed.host,
        path: parsed.path,
        headers: {
          origin: 'https://www.kdocs.cn',
          referer: referer_str,
          "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36 OPR/68.0.3618.173"
        },
        protocol: parsed.protocol
      }, (err, response) => {
        st.bytesRead
        if (err) {
          return resolve({
            ok: false,
            msg: err.message + "IN form.submit callback"
          })
        }
        if (!response.headers.etag) {
          return resolve({
            ok: false,
            msg: `empty etag`
          })
        }
        if (!response.headers.newfilename) {
          return resolve({
            ok: false,
            msg: `empty newfilename`
          })
        }
        let etag = response.headers.etag;
        etag = etag.replace(/"/g, "")
        let newfilename_sha1 = response.headers.newfilename;
        return resolve({
          ok: true, msg: "ok",
          data: {
            etag: etag,
            newfilename_sha1: newfilename_sha1,
            name: fpParsed.base,
            size: st.bytesRead || o_stats.stats.size
          }
        })
      });
      form.on("error", (err) => {
        return resolve({
          ok: false,
          msg: `form.on(error):${err.message}`
        })
      });
    })
  }

  /**
   * @returns {Promise<{ok:Boolean,
   * msg:String,
   * data:{
   * newfilename_sha1:String,
   * etag:String,
   * nameAsXlsx:String,
   * combineSize:Number
   * }}>}
   * @param {String} rar_filepath 
   * @param {Number} folder_id 
   */
  formUploadRarFileAsXlsx(rar_filepath, folder_id = 0) {
    let rarpathparsed = path.parse(rar_filepath);

    return new Promise(async resolve => {
      if (!rarpathparsed.base.endsWith(".rar")) {
        return resolve({
          ok: false,
          msg: `NOT RAR FILE:${rar_filepath}`
        })
      }
      let HEAD_XLSX_PATH = path.join(__dirname, "../../DUMMY/head.xlsx");
      let o_statsHead = await Toolbox.getStats(HEAD_XLSX_PATH);
      if (!o_statsHead.ok) {
        return resolve({
          ok: false,
          msg: o_statsHead.msg
        })
      }
      let o_statsRarFile = await Toolbox.getStats(rar_filepath);
      if (!o_statsRarFile.ok) {
        return resolve({
          ok: false,
          msg: o_statsHead.msg
        })
      }
      let combinesize = o_statsRarFile.stats.size + o_statsHead.stats.size;
      let nameAsXlsx = rarpathparsed.base.replace(/\.rar$/, ".xlsx");
      if (nameAsXlsx.startsWith(".")) {
        nameAsXlsx = "UNNAMED" + nameAsXlsx
      }
      if (combinesize >= 1024 * 1024 * 1024) {
        return resolve({
          ok: false,
          msg: `文件太大,和头文件加起来超过1GB`
        })
      }
      let o_token = await this.requestUploadToken(folder_id, combinesize, nameAsXlsx);
      // debugger
      if (!o_token.ok) {
        return resolve({
          ok: false,
          msg: `fail to get token:${o_token.msg
            }`
        })
      }
      let cStream = CombinedStream.create();
      let f1 = fs.createReadStream(HEAD_XLSX_PATH);
      let f2 = fs.createReadStream(rar_filepath);
      f2.on("error", (err) => {
        return resolve({
          ok: false,
          msg: `f2 stream error:${err.message}`
        })
      })
      cStream.append(f1);
      cStream.append(f2);
      let form = new FormData();
      form.append("key", o_token.data.key);
      form.append("Policy", o_token.data.Policy);
      form.append("submit", "Upload to KS3");
      form.append("Signature", o_token.data.Signature);
      form.append("KSSAccessKeyId", o_token.data.KSSAccessKeyId);
      form.append("x-kss-newfilename-in-body", o_token.data["x-kss-newfilename-in-body"]);
      form.append("x-kss-server-side-encryption", o_token.data["x-kss-server-side-encryption"]);
      form.append("file", cStream, {
        filename: nameAsXlsx,
        contentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        knownLength: combinesize
      });
      let url = require("url");
      let parsed = url.parse(o_token.data.url);
      form.submit({
        host: parsed.host,
        path: parsed.path,
        headers: {
          Origin: 'https://www.kdocs.cn',
          Referer: folder_id ? `https://www.kdocs.cn/mine/${folder_id}` : "https://www.kdocs.cn/?show=all",
          "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36 OPR/68.0.3618.173"
        },
        protocol: parsed.protocol
      }, (err, response) => {
        if (err) {
          return resolve({
            ok: false,
            msg: err.message + "IN form.submit callback"
          })
        }
        if (!response.headers.etag) {
          return resolve({
            ok: false,
            msg: `empty etag`
          })
        }
        if (!response.headers.newfilename) {
          return resolve({
            ok: false,
            msg: `empty newfilename`
          })
        }
        let etag = response.headers.etag;
        etag = etag.replace(/"/g, "")
        let newfilename_sha1 = response.headers.newfilename;
        return resolve({
          ok: true,
          msg: 'ok',
          data: {
            etag: etag,
            newfilename_sha1: newfilename_sha1,
            nameAsXlsx: nameAsXlsx,
            combineSize: combinesize
          }
        })
        debugger
      });
      form.on("error", (err) => {
        return resolve({
          ok: false,
          msg: `form.on(error):${err.message}`
        })
      })
    })
  }


  /**
   * @returns {Promise<{ok:Boolean,
    * msg:String,
    * data:{
    * newfilename_sha1:String,
    * etag:String,
    * size:Number
    * }}>}
   * @param {String} param_fname 
   * @param {Number} folder_id 
   */
  formUploadRandomXlsx(param_fname = Math.random().toString().substr(0, 5) + "给力.xlsx", folder_id = 0) {
    let referer_str = folder_id ? `https://www.kdocs.cn/mine/${folder_id}` : "https://www.kdocs.cn/?show=all";
    let garbage_xlsx = require("../../tool/garbage_xlsx");

    return new Promise(resolve => {
      let innerPromiseGen = () => {
        /**
         * @type {Promise<{ok:Boolean,msg:String,data:{
          * newfilename_sha1:String,
          * etag:String,
          * size:Number
         * }}>}
         */
        let p = new Promise(async rrr => {
          let ramdom_xlsx = garbage_xlsx.generateXlsx();
          let o_stats = await Toolbox.getStats(ramdom_xlsx);
          if (!o_stats.ok) {
            return rrr({
              ok: false,
              msg: `cant get stats of random xlsx:${o_stats.msg}`
            })
          }
          let o_token = await this.requestUploadToken(folder_id, o_stats.stats.size, param_fname);
          if (!o_token.ok) {
            return rrr({
              ok: false,
              msg: `fail get token:${o_token.msg}`
            })
          }
          let fstream = fs.createReadStream(ramdom_xlsx, { flags: "r", emitClose: true });
          fstream.on("error", (err) => {
            return rrr({
              ok: false,
              msg: `fstream error:${err.message}`
            })
          })
          let form = new FormData();
          form.append("key", o_token.data.key);
          form.append("Policy", o_token.data.Policy);
          form.append("submit", "Upload to KS3");
          form.append("Signature", o_token.data.Signature);
          form.append("KSSAccessKeyId", o_token.data.KSSAccessKeyId);
          form.append("x-kss-newfilename-in-body", o_token.data["x-kss-newfilename-in-body"]);
          form.append("x-kss-server-side-encryption", o_token.data["x-kss-server-side-encryption"]);
          form.append("file", fstream, {
            filename: param_fname,
            contentType: "application/octet-stream",
          });
          let url = require("url");
          let parsed = url.parse(o_token.data.url);
          form.submit({
            host: parsed.host,
            path: parsed.path,
            headers: {
              Origin: 'https://www.kdocs.cn',
              Referer: referer_str,
              "user-agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36 OPR/68.0.3618.173"
            }
          }, (err, response) => {
            if (err) {
              return rrr({
                ok: false,
                msg: err.message + "IN form.submit callback"
              })
            }
            if (!response.headers.etag) {
              return rrr({
                ok: false,
                msg: `empty etag`
              })
            }
            if (!response.headers.newfilename) {
              return rrr({
                ok: false,
                msg: `empty newfilename`
              })
            }
            let etag = response.headers.etag;
            etag = etag.replace(/"/g, "")
            let newfilename_sha1 = response.headers.newfilename;
            return rrr({
              ok: true,
              msg: 'ok',
              data: {
                etag: etag,
                newfilename_sha1: newfilename_sha1,
                size: o_stats.stats.size
              }
            })


          });
          form.on("error", (err) => {
            return rrr({
              ok: false,
              msg: `form.on(error):${err.message}`
            })
          });
          fstream.on("close", () => {
            console.log("random xlsx close...")

          })

        });
        p.then(o => {
          resolve(o);
        })
        return p;
      };
      this.random_xlsx_queue.add(innerPromiseGen);
    })
  }

  /**
   * @returns {Promise<{
   * ok:Boolean,msg:String,
   * data:{
   * fsha:String,
   * fsize:Number,
   * fileid:Number
   * }
   * }>}
   * @param {String} fname 
   * @param {String} sha1_newfilename 
   * @param {String} etag 
   * @param {Number} fsize >0
   * @param {Number} folder_id 
   */
  createNewFile(fname, sha1_newfilename, etag, fsize, folder_id = 0) {
    return new Promise(resolve => {
      let data = buffer.Buffer.from(JSON.stringify({
        csrfmiddlewaretoken: this.csrf_token,
        etag: etag,
        groupid: this.mycloud_groupid,
        isUpNewVer: false,
        name: fname,
        parent_path: [],
        parentid: folder_id,
        sha1: sha1_newfilename,
        size: fsize,
        store: "ks3",
      }))
      this.axios.post(`https://www.kdocs.cn/3rd/drive/api/v5/files/file`, data, {
        headers: {
          cookie: this.cookie_as_header,
          referer: folder_id ? `https://www.kdocs.cn/mine/${folder_id}` : "https://www.kdocs.cn/?show=all",
          origin: 'https://www.kdocs.cn',
          "Content-Length": data.byteLength
        }
      }).then(axresp => {
        // debugger
        if (axresp.data.result == "ok") {
          this.getSpaces();
          this.getFilesListInFolder(folder_id);
          return resolve({
            ok: true,
            msg: "ok",
            data: {
              fileid: axresp.data.id,
              fsize: axresp.data.fsize,
              fsha: axresp.data.fsha
            }
          })
        }
        throw axresp.data;
      }).catch(axerr => {
        CommonAHG(resolve)(axerr);
      })
    })
  }

  /**
   * @returns {Promise<{ok:Boolean,msg:String,
   * data:{
   * mtime:Date,
   * fname:String,
   * fsize:Number,
   * fver_now:Number
   * }
   * }>}
   * @param {Number} fileid 
   * @param {String} etag 
   * @param {String} sha1_newfilename 
   * @param {Number} size 
   * @param {Number} folder_id
   */
  updateFileToNewVersion(fileid, etag, sha1_newfilename, size, folder_id = 0) {
    let referer_str = folder_id ?
      `https://www.kdocs.cn/mine/${folder_id}`
      : "https://www.kdocs.cn/?show=all";
    let databuf = buffer.Buffer.from(JSON.stringify({
      csrfmiddlewaretoken: this.csrf_token,
      etag: etag,
      fileid: String(fileid),
      sha1: sha1_newfilename,
      size: size,
      store: "ks3"
    }));
    this.getHistories(fileid);
    return new Promise(resolve => {
      this.axios.put(`https://www.kdocs.cn/3rd/drive/api/v5/files/file`, databuf, {
        headers: {
          'content-type': 'application/json;charset=UTF-8',
          cookie: this.cookie_as_header,
          origin: 'https://www.kdocs.cn',
          referer: referer_str,
          'content-length': databuf.byteLength
        }
      }).then(axresp => {
        if (axresp.data && axresp.data.result == "ok") {
          this.getPath(fileid);
          this.getSpaces();
          this.getHistories(fileid);
          this.getFilesListInFolder(0);
          return resolve({
            ok: true,
            msg: "ok",
            data: {
              mtime: new Date(axresp.data.mtime * 1000),
              fname: axresp.data.fname,
              fsize: axresp.data.fsize,
              fver_now: axresp.data.fver
            }
          })
        }
        throw axresp.data;
      }).catch(axerr => {
        CommonAHG(resolve)(axerr);
      })
    })
  }

  /**
   * @returns {Promise<{ok:Boolean,msg:String,data:{
   * folder_id:Number
   * }}>}
   * @param {String} name 
   * @param {Number} parent_id 
   */
  createFolder(name, parent_id = 0) {
    let referer_str = parent_id ? `https://www.kdocs.cn/mine/${parent_id}` : "https://www.kdocs.cn/?show=all";
    return new Promise(resolve => {
      let databuf = buffer.Buffer.from(JSON.stringify({
        csrfmiddlewaretoken: this.csrf_token,
        groupid: String(this.mycloud_groupid),
        name: name,
        owner: true,
        parentid: parent_id,
        parsed: true,
      }));
      this.axios.post(`https://www.kdocs.cn/3rd/drive/api/v5/files/folder`, databuf, {
        headers: {
          cookie: this.cookie_as_header,
          origin: "https://www.kdocs.cn",
          referer: referer_str,
          'content-length': databuf.byteLength,
          'content-type': "application/json;charset=UTF-8"
        }
      }).then(axresp => {
        // debugger
        if (axresp.data.result == "ok" && axresp.data.id) {
          this.USELESS_GET_REQUEST('https://www.kdocs.cn/3rd/account/p/bind/status', referer_str)
          return resolve({
            ok: true,
            msg: "ok",
            data: {
              folder_id: axresp.data.id
            }
          })
        }
        throw axresp.data;
      }).catch(axerr => {
        CommonAHG(resolve)(axerr);
      })
    })
  }


  USELESS_metadata_of_folder(folder_id) {
    this.axios.get(`https://www.kdocs.cn/3rd/drive/api/v5/files/${folder_id}/metadata`, {
      headers: {
        cookie: this.cookie_as_header,
        referer: `referer: https://www.kdocs.cn/mine/${folder_id}`
      }
    }).then(r => { }).catch(r => { })
  }

  /**
   * 
   * @param {String} referer_str 
   */
  USELESS_tags_of_group(referer_str) {
    this.axios.get(`https://www.kdocs.cn/3rd/drive/api/v3/tags/1/groups?groupids=${groupid}`, {
      headers: {
        cookie: this.cookie_as_header,
        referer: referer_str || "https://www.kdocs.cn/?show=all"
      }
    }).then(r => { }).catch(r => { })
  }

  /**
   * 
   * @param {Number[]} fileids 
   * @param {String} referer_str 
   * @param {Boolean} need_2
   */
  USELESS_tags_of_files(fileids, referer_str, need_2 = false) {
    this.axios.get(`https://www.kdocs.cn/3rd/drive/api/v3/tags/1/files`, {
      params: {
        fileids: fileids.join(",")
      },
      headers: {
        cookie: this.cookie_as_header,
        referer: referer_str || "https://www.kdocs.cn/?show=all"
      }
    }).then(r => { }).catch(r => { });
    if (!need_2) {
      return;
    }
    this.axios.get(`https://www.kdocs.cn/3rd/drive/api/v3/tags/2/files`, {
      params: {
        fileids: fileids.join(",")
      },
      headers: {
        cookie: this.cookie_as_header,
        referer: referer_str || "https://www.kdocs.cn/?show=all"
      }
    }).then(r => { }).catch(r => { })
  }

  /**
   * 
   * @param {String} referer_str 
   */
  USELESS_user_config(referer_str) {
    this.axios.get(`ttps://www.kdocs.cn/kd/api/user/config?key=shareGuide`, {
      params: {
        // fileids: fileids.join(",")
      },
      headers: {
        cookie: this.cookie_as_header,
        referer: referer_str || "https://www.kdocs.cn/?show=all"
      }
    }).then(r => { }).catch(r => { })
  }

  /**
   * 
   * @param {String} link 
   * @param {String} referer_str 
   */
  USELESS_GET_REQUEST(link, referer_str) {
    this.axios.get(link, {
      params: {
        // fileids: fileids.join(",")
      },
      headers: {
        cookie: this.cookie_as_header,
        referer: referer_str || "https://www.kdocs.cn/?show=all"
      }
    }).then(r => { }).catch(r => { })
  }
}

class Kdv2App {
  /**
   * 
   * @param {KDV2} kdv2 
   */
  constructor(kdv2) {
    this.kd = kdv2;
  }

  /**
   * @description 上传文件并用小版本覆盖
   * @returns {Promise<{ok:Boolean,msg:String,data:{
   * fileid:Number,
   * fname:String,
   * fsha1:String,
   * fsize:Number
   * }}>}
   * @param {String} filepath 
   * @param {Number} folder_id 
   */
  uploadFilePlusSmallFileVersion(filepath, folder_id = 0) {
    let fpParsed = path.parse(filepath);
    return new Promise(async resolve => {
      if (fpParsed.base.endsWith(".rar")) {
        let o_upv1 = await this.kd.formUploadRarFileAsXlsx(filepath, folder_id);
        if (!o_upv1.ok) {
          return resolve({ ok: false, msg: `kd.formUploadRarFileAsXlsx:${o_upv1.msg}` })
        }
        let o_create = await this.kd.createNewFile(o_upv1.data.nameAsXlsx, o_upv1.data.newfilename_sha1, o_upv1.data.etag, o_upv1.data.combineSize, folder_id);
        if (!o_create.ok) {
          return resolve({
            ok: false, msg: `kd.createNewFile:${o_create.msg}`
          })
        }
        let o_upv2 = await this.kd.formUploadRandomXlsx(o_upv1.data.nameAsXlsx, folder_id);
        if (!o_upv2.ok) {
          return resolve({
            ok: false,
            msg: `kd.formUploadRandomXlsx:${o_upv2.msg}`
          })
        }
        let o_update = await this.kd.updateFileToNewVersion(o_create.data.fileid, o_upv2.data.etag, o_upv2.data.newfilename_sha1, o_upv2.data.size, folder_id);
        if (!o_update.ok) {
          return resolve({
            ok: false,
            msg: `kd.updateFileToNewVersion:${o_update.msg}`
          })
        }
        return resolve({
          ok: true, msg: "ok",
          data: {
            fileid: o_create.data.fileid,
            fname: o_upv1.data.nameAsXlsx,
            fsha1: o_create.data.fsha,
            fsize: o_create.data.fsize
          }
        })

      }
      let o_upv1 = await this.kd.formUploadNormalFile(filepath, folder_id, false);
      if (!o_upv1.ok) {
        return resolve({
          ok: false,
          msg: `kd.formUploadNormalFile:${o_upv1.msg}`
        })
      }
      let o_create = await this.kd.createNewFile(o_upv1.data.name,
        o_upv1.data.newfilename_sha1,
        o_upv1.data.etag,
        o_upv1.data.size,
        folder_id);
      if (!o_create.ok) {
        return resolve({
          ok: false, msg: `kd.createNewFile:${o_create.msg}`
        })
      }
      let o_upv2 = await this.kd.formUploadNormalFile(filepath, folder_id, true);
      if (!o_upv2.ok) {
        return resolve({
          ok: false, msg: `o_upv2(normal file):${o_upv2.msg}`
        })
      }
      let o_update = await this.kd.updateFileToNewVersion(o_create.data.fileid, o_upv2.data.etag, o_upv2.data.newfilename_sha1, o_upv2.data.size, folder_id);
      if (!o_update.ok) {
        return resolve({
          ok: false,
          msg: `kd.updateFileToNewVersion:${o_update.msg}`
        })
      }
      return resolve({
        ok: true, msg: "ok",
        data: {
          fileid: o_create.data.fileid,
          fname: o_upv1.data.name,
          fsha1: o_create.data.fsha,
          fsize: o_create.data.fsize
        }
      })
    })
  }

  /**
   * 
   * @param {String} dirPath 
   * @param {Number} parent_id 
   */
  uploadDir_WithSafeName(dirPath, parent_id = 0) {
    return new Promise(resolve=>{
      let basename = path.basename(dirPath);
      let fakeDirName = basename;
      if(Kuroshiro.Util.hasJapanese(fakeDirName)){
        fakeDirName = Kuroshiro.Util.kanaToRomaji(fakeDirName)
      }
    })
  }



}


/**
 * @returns {Promise<{ok:Boolean,msg:String,
 * data:{
 * kdv2:KDV2
 * }}>}
 * @param {String} wps_sid 
 */
function GetKdV2ByWpssid(wps_sid) {
  return new Promise(resolve => {
    let kd = new KDV2();
    kd.axios.get("https://www.kdocs.cn/?show=all", {
      headers: {
        cookie: `wps_sid=${wps_sid}`,
        accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
      }
    }).then(axresp => {
      if (axresp.headers
        && axresp.headers["set-cookie"]) {
        let parsed = setCookieParser.parse(axresp.headers["set-cookie"]);
        if (parsed && parsed.length) {
          let hasValue = parsed.filter(e => !!e.value);
          hasValue.forEach(e => {
            kd.__appendCookies.push({
              name: e.name,
              value: e.value
            })
          })
        }
      }
      let matched_groupid = axresp.data.match(/"myCloud":{"id":([0-9]+),/);
      // debugger
      if (matched_groupid && matched_groupid.length == 2) {
        let groupid = Number(matched_groupid[1]);
        kd.mycloud_groupid = groupid;
      } else {
        return resolve({
          ok: false,
          msg: `cant't get groupid from HTML`
        })
      }
      // debugger
      kd.wps_sid = wps_sid;
      kd.getSpaces();
      kd.USELESS_GET_REQUEST('https://www.kdocs.cn/3rd/plus/ops/opsd/api/v1/policy?window_key=web_application_list');
      kd.USELESS_GET_REQUEST('https://www.kdocs.cn/kd/api/scenes?types=pcWebNewPage,pcWebRecent');
      kd.USELESS_GET_REQUEST('https://www.kdocs.cn/3rd/open/serviceapi/third/menu');
      kd.USELESS_GET_REQUEST("https://www.kdocs.cn/3rd/plus/sysadmin/api/v1/me/operations/strategies?positions=web_left_enterprise_below_entrance");
      kd.USELESS_GET_REQUEST("https://www.kdocs.cn/3rd/drive/api/v3/tags/2/items?offset=0&count=20");
      kd.USELESS_GET_REQUEST("https://www.kdocs.cn/kd/api/configure/list?idList=showNoviceGuide");
      kd.USELESS_GET_REQUEST("https://www.kdocs.cn/kd/api/configure/list?idList=guidedesktop");
      kd.USELESS_GET_REQUEST("https://www.kdocs.cn/kd/api/user/config?key=shareGuide");
      kd.USELESS_GET_REQUEST('https://www.kdocs.cn/kd/api/user/config?key=jinxiaomenghello');
      kd.USELESS_GET_REQUEST('https://www.kdocs.cn/kd/api/user/config?key=spaceFull');
      // kd.USELESS_GET_REQUEST("https://www.kdocs.cn/kd/api/configure/list?idList=messageConfig&_t=1595087821632");
      // kd.USELESS_GET_REQUEST(""); 
      // kd.USELESS_GET_REQUEST(""); 
      // kd.USELESS_GET_REQUEST(""); 
      // kd.USELESS_GET_REQUEST(""); 
      // kd.USELESS_GET_REQUEST("");
      return resolve({
        ok: true, msg: "ok",
        data: {
          kdv2: kd
        }
      })
      // throw axresp;
      debugger
    }).catch(axerr => {
      if (axerr.response && axerr.response.status == 302) {
        return resolve({
          ok: false,
          msg: `HTTP 302:${axerr.response.headers.location}`
        })
      }

      return CommonAHG(resolve)(axerr);
    })
  })
}


/**
 * 
 * @returns {string} 得到一个客户端生成的CSRF TOKEN
 */
function CreateCsrfToken() {
  let e = "";
  for (var n = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678", r = n.length, i = 0; i < 32; i++) {
    e += n.charAt(Math.floor(Math.random() * r));
  }
  return e;
}

module.exports = {
  GetKdV2ByWpssid
}